package com.terra.ns.imp.common.log

import cn.hutool.core.util.IdUtil
import cn.hutool.extra.spring.SpringUtil
import cn.hutool.json.JSONUtil
import com.terra.ns.imp.common.satoken.utils.LoginUtils
import com.terra.ns.imp.common.util.exception.hasExceptionReturnNull
import com.terra.ns.imp.common.util.http.IpUtils
import jakarta.servlet.http.HttpServletResponse
import org.aspectj.lang.JoinPoint
import org.aspectj.lang.annotation.*
import org.aspectj.lang.reflect.MethodSignature
import org.slf4j.LoggerFactory
import org.springframework.boot.autoconfigure.AutoConfiguration
import org.springframework.web.context.request.RequestContextHolder
import org.springframework.web.context.request.ServletRequestAttributes
import org.springframework.web.multipart.MultipartFile
import java.time.LocalDateTime

/**
@author qins
@date 2023/6/2
@desc 接口操作日志切面
 */
@Aspect
@AutoConfiguration
class OperateLogAspect {

    private val log = LoggerFactory.getLogger(OperateLogAspect::class.java)
    private val reqTrace: ThreadLocal<Map<String, Any>> = ThreadLocal()
    private val traceIdKey = "traceId"
    private val startTimeKey = "startTime"

    /**
     * 定义切入点
     */
    @Pointcut("execution(* com.terra.ns.imp.**.controller..*.*(..))")
    fun requestLogPoint(){}

    @Before("requestLogPoint()")
    fun doBefore(joinPoint: JoinPoint) {
        val traceId = IdUtil.getSnowflakeNextId()
        reqTrace.set(mapOf(Pair(traceIdKey, traceId), Pair(startTimeKey, System.currentTimeMillis())))
        log.info("=============traceId: $traceId, Request Start=============")
    }

    @AfterReturning(pointcut = "requestLogPoint()", returning = "result")
    fun doReturnReturning(joinPoint: JoinPoint, result: Any?) {
        handleLog(joinPoint, result, null)
        val trace = reqTrace.get()
        reqTrace.remove()
        log.info("=============traceId: ${trace[traceIdKey]}, Request End, 处理时长：${System.currentTimeMillis() - trace[startTimeKey] as Long}ms=============")
    }

    @AfterThrowing(pointcut = "requestLogPoint()", throwing = "ex")
    fun doAfterThrowing(joinPoint: JoinPoint, ex: Exception) {
        handleLog(joinPoint, null, ex)
        val trace = reqTrace.get()
        reqTrace.remove()
        log.info("=============traceId: ${trace[traceIdKey]}, Request End, Exception=============")
    }

    /**
     * 记录操作日志
     */
    private fun handleLog(joinPoint: JoinPoint, returnResult: Any?, ex: Exception?) {
        val webLogProperties = SpringUtil.getBean(OperateLogProperties::class.java)
        val attributes = RequestContextHolder.currentRequestAttributes() as ServletRequestAttributes
        val request = attributes.request
        val url = request.requestURL.toString()  //请求url
        val ip =  IpUtils.getRequestIp(request) //请求IP
        val methodSignature = joinPoint.signature as MethodSignature
        val method = methodSignature.method
        //解析参数
        val param = doLogRequestParam(joinPoint)
        val paramStr = JSONUtil.toJsonStr(param)
        if(webLogProperties.print) {

            log.info("IP                 : {}", ip)
            log.info("URL                : {}", url)
            log.info("Class Method       : {}", method.name)
            log.info("Request Param      : {}", paramStr)
        }
        //判断是否需要保存操作日志
        if(method.isAnnotationPresent(OperateLog::class.java) && webLogProperties.save) {
            //获取注解的值
            val annotation = method.getAnnotation(OperateLog::class.java)
            val logEvent = convertLogEvent(ip, url, paramStr, method.name, annotation, returnResult, ex)
            logEvent.handleTime = System.currentTimeMillis() - reqTrace.get()[startTimeKey] as Long
            SpringUtil.publishEvent(logEvent)
        }
    }

    private fun convertLogEvent(
        ip: String,
        url: String,
        paramStr: String,
        methodName: String,
        annotation: OperateLog,
        result: Any?,
        ex: Exception?
    ) : OperateLogEvent {
        val log = OperateLogEvent()
        log.traceId = reqTrace.get()[traceIdKey].toString()
        log.moduleName = annotation.module
        log.operateType = annotation.operateType.type
        log.operatorIp = ip
        log.requestUrl = url
        log.requestMethod = methodName
        log.requestParam = paramStr
        log.operatorName = hasExceptionReturnNull { LoginUtils.getUserName() }
        log.operatorAccount = hasExceptionReturnNull { LoginUtils.getUserCode() }
        if(ex != null) {
            val exMsg = ex.message ?: ""
            log.operateResult = OperateResultEnum.FAIL.result
            log.resultMessage = exMsg.substring(0, 1000.coerceAtMost(exMsg.length))
        } else {
            log.operateResult = OperateResultEnum.SUCCESS.result
            log.resultMessage = OperateResultEnum.SUCCESS.desc
        }
        if(annotation.saveResponseData && result != null) {
            val resultStr = JSONUtil.toJsonStr(result)
            log.responseData = resultStr.substring(0, 1000.coerceAtMost(resultStr.length))
        }
        log.operateTime = LocalDateTime.now()
        return log
    }

    /**
     *记录请求参数操作方法
     *@return 请求参数json格式化串
     **/
    private fun doLogRequestParam(point: JoinPoint): Map<String, String?> {
        val paramNameList = (point.signature as MethodSignature).parameterNames
        val args = point.args
        val param = HashMap<String, String?>()
        args.forEachIndexed foreachStart@{ index, item ->
            if(item is HttpServletResponse) return@foreachStart
            val paramVal = if (item is MultipartFile) {
                item.originalFilename
            } else {
                if(item != null) JSONUtil.toJsonStr(item) else item
            }
            param[paramNameList[index]] = paramVal
        }
        return param
    }

}